// TrackViewNodes.cpp : implementation file
//

#include "StdAfx.h"
#include "TrackViewNodes.h"
#include "TrackViewDopeSheet.h"
#include "TrackViewUndo.h"
#include "StringDlg.h"
#include "NumberDlg.h"
#include "TVEventsDialog.h"
#include "TrackViewDialog.h"

#include "Clipboard.h"

#include "IMovieSystem.h"

#include "Objects\EntityObject.h"
#include "ViewManager.h"
#include "RenderViewport.h"
#include "ExportColladaDlg.h"

// The 'MI' represents a Menu Item.
#define MI_SELECT_IN_VIEWPORT 603
#define MI_COPY_SELECTED_NODES 602
#define MI_REMOVE_SELECTED 10
#define MI_EXPAND_ALL 650
#define MI_COLLAPSE_ALL 659
#define MI_EXPAND_FOLDERS 660
#define MI_COLLAPSE_FOLDERS 661
#define MI_EXPAND_ENTITIES 651
#define MI_COLLAPSE_ENTITIES 652
#define MI_EXPAND_CAMERAS 653
#define MI_COLLAPSE_CAMERAS 654
#define MI_EXPAND_MATERIALS 655
#define MI_COLLAPSE_MATERIALS 656
#define MI_EXPAND_EVENTS 657
#define MI_COLLAPSE_EVENTS 658
#define MI_RENAME 11
#define MI_CREATE_FOLDER 610
#define MI_ADD_SELECTED_ENTITY 500
#define MI_ADD_DIRECTOR_NODE 501
#define MI_ADD_CONSOLE_VARIABLE 502
#define MI_ADD_SCRIPT_VARIABLE 503
#define MI_ADD_MATERIAL 504
#define MI_ADD_EVENT 505
#define MI_ADD_CURRENT_LAYER 506
#define MI_ADD_COMMENT_NODE 507
#define MI_ADD_RADIALBLUR 508
#define MI_ADD_COLLORCORRECTION 509
#define MI_ADD_DOF 510
#define MI_ADD_SCREENFADER_NODE 511
#define MI_ADD_HDR_SETUP 512
#define MI_ADD_SHADOW_SETUP 513
#define MI_EDIT_EVENTS 550
#define MI_PASTE_NODES 604
#define MI_DETACH_FROM_GROUP 611
#define MI_COPY_SELECTED_KEYS 600
#define MI_PASTE_KEYS 601
#define MI_SAVE_TO_COLLADA 12
#define MI_LOAD_FROM_COLLADA 14
#define MI_SET_AS_VIEW_CAMERA 13
#define MI_SET_AS_ACTIVE_DIRECTOR 15
#define MI_TRANSFORM_BONE_LINK 16
#define MI_ADD_TRACK_BASE 1000
#define MI_REMOVE_TRACK 299
#define MI_SHOW_HIDE_BASE 100
#define MI_SELECT_SUBMTL_BASE 2000


#define EDIT_DISABLE_GRAY_COLOR RGB(180,180,180)

#define EDIT_DISABLE_GRAY_COLOR RGB(180,180,180)

//////////////////////////////////////////////////////////////////////////
CTrackViewNodesCtrl::CAnimSequenceUndo::CAnimSequenceUndo( IAnimSequence *pSeq,const char *sName )
: CUndo( sName )
{
	CUndo::Record( new CUndoAnimSequenceObject(pSeq) );
}

BEGIN_MESSAGE_MAP(CTrackViewNodesCtrl, CTreeCtrlReport)
	ON_NOTIFY_REFLECT(NM_RCLICK, OnNMRclick)
	ON_NOTIFY_REFLECT(XTP_NM_REPORT_FOCUS_CHANGING, OnFocusChanging)
	ON_WM_SIZE()
	ON_WM_ERASEBKGND()
	ON_WM_MOUSEMOVE( )
	ON_MESSAGE(ENAC_UPDATE, OnFilterChange)
	ON_WM_KEYDOWN()
END_MESSAGE_MAP()

//////////////////////////////////////////////////////////////////////////
CTrackViewNodesCtrl::CTrackViewNodesCtrl()
{
	m_sequence = 0;
	m_keysCtrl = 0;
	m_currentMatchIndex = 0;
	m_matchCount = 0;

	CMFCUtils::LoadTrueColorImageList( m_imageList,IDB_TRACKVIEW_NODES,16,RGB(255,0,255) );
	SetImageList( &m_imageList );

	CXTPReportColumn *pNodeCol   
		= AddColumn(new CXTPReportColumn(0, _T("Node"), 150, TRUE, XTP_REPORT_NOICON, TRUE, TRUE));
	pNodeCol->SetTreeColumn(true);
	pNodeCol->SetSortable(FALSE);

	ShowHeader(FALSE);

	SetMultipleSelection(TRUE);

	SetGroupRowsBold(TRUE);
	//SetTreeIndent(30);

	GetReportHeader()->AllowColumnRemove(FALSE);
	GetReportHeader()->AllowColumnSort(FALSE);

	AllowEdit(FALSE);

	m_bEditLock = false;

	m_downCursor = AfxGetApp()->LoadCursor(IDC_ARROW_DOWN);
	m_upCursor = AfxGetApp()->LoadCursor(IDC_ARROW_UP);
	m_downRightCursor = AfxGetApp()->LoadCursor(IDC_ARROW_DOWNRIGHT);
	m_upRightCursor = AfxGetApp()->LoadCursor(IDC_ARROW_UPRIGHT);
};

//////////////////////////////////////////////////////////////////////////
void CTrackViewNodesCtrl::Init()
{
	m_filterEdit.Create("Edit", NULL, WS_VISIBLE|WS_CHILD|WS_BORDER, CRect(0,0,100,20), this, 1);
	m_filterEdit.Init();
	m_filterEdit.SetMode(_MODE_CURSOR_O_LIST_);
	m_matchCountStatic.Create("0/0", WS_VISIBLE|WS_CHILD|SS_CENTER, CRect(70,0,100,20), this, 2);
}

//////////////////////////////////////////////////////////////////////////
void CTrackViewNodesCtrl::SetSequence( IAnimSequence *seq )
{
	m_pTrackViewDialog = (CTrackViewDialog*)GetOwner();

	m_itemInfos.clear();
	DeleteAllItems();
	m_sequence = seq;

	FillAutoCompletionListForFilter(m_sequence);

	Reload();
	m_keysCtrl->Invalidate();
}

//////////////////////////////////////////////////////////////////////////
void CTrackViewNodesCtrl::Reload()
{
	__super::Reload();
	SyncKeyCtrl();
	SetLayerNodeAnimators();
}

//////////////////////////////////////////////////////////////////////////
void CTrackViewNodesCtrl::SetKeyListCtrl( CTrackViewDopeSheetBase *keysCtrl )
{
	m_keysCtrl = keysCtrl;
	//SyncKeyCtrl();
}

//////////////////////////////////////////////////////////////////////////
void CTrackViewNodesCtrl::FillTracks( CRecord *pNodeRecord,IAnimNode *node )
{
	IAnimNode::SParamInfo paramInfo;
	for (int i = 0; i < node->GetTrackCount(); i++)
	{
		IAnimTrack *track = node->GetTrackByIndex(i);
		if (track->GetFlags() & IAnimTrack::ATRACK_HIDDEN) 
			continue;

		int type = track->GetParameterType();

		if (!node->GetParamInfoFromId(type,paramInfo))
			continue;

		int nImage = GetIconIndexForParam(type, node->GetType());
		CRecord *pTrackRecord = new CRecord( false,paramInfo.name );
		pTrackRecord->SetItemHeight( 18 );

		CXTPReportRecordItem *pTrackItem = new CXTPReportRecordItemText(paramInfo.name);
		pTrackRecord->AddItem(pTrackItem);
		pTrackItem->SetIconIndex(nImage);
		pTrackItem->SetTextColor(::GetSysColor(COLOR_HIGHLIGHT));

		pTrackRecord->node = node;
		pTrackRecord->baseObj = pNodeRecord->baseObj;
		pTrackRecord->track = track;
		pTrackRecord->paramId = type;

		m_itemInfos.push_back(pTrackRecord);

		pNodeRecord->GetChilds()->Add( pTrackRecord );

		if (track->GetSubTrackCount() > 0)
		{
			// This track s compound
			for (int nSubtrack = 0; nSubtrack < track->GetSubTrackCount(); nSubtrack++)
			{
				IAnimTrack *pSubTrack = track->GetSubTrack(nSubtrack);

				const char *name = track->GetSubTrackName(nSubtrack);

				int nSubImage = GetIconIndexForParam(type, node->GetType());
				CRecord *pSubTrackRecord = new CRecord( false,name );
				pSubTrackRecord->SetItemHeight( 18 );

				CXTPReportRecordItem *pSubTrackItem = new CXTPReportRecordItemText(name);
				pSubTrackRecord->AddItem(pSubTrackItem);
				pSubTrackItem->SetIconIndex(nSubImage);
				pSubTrackItem->SetTextColor(::GetSysColor(COLOR_HIGHLIGHT));

				pSubTrackRecord->node = node;
				pSubTrackRecord->baseObj = pNodeRecord->baseObj;
				pSubTrackRecord->track = pSubTrack;
				pSubTrackRecord->m_bIsSubTrack = true;
				pSubTrackRecord->paramId = pSubTrack->GetParameterType();

				m_itemInfos.push_back(pSubTrackRecord);

				pTrackRecord->GetChilds()->Add( pSubTrackRecord );	
			}
			if (track->GetFlags() & IAnimTrack::ATRACK_EXPANDED)
				pTrackRecord->SetExpanded(TRUE);
		}
	}
}

//////////////////////////////////////////////////////////////////////////
int CTrackViewNodesCtrl::GetIconIndexForParam( int type, EAnimNodeType nodeType ) const
{
	int nImage = 13; // Default

	// If it's a track which belongs to the post-fx node,
	// just use a default icon.
	if (nodeType == ANODE_RADIALBLUR
		|| nodeType == ANODE_COLORCORRECTION
		|| nodeType == ANODE_DOF
		|| nodeType == ANODE_HDRSETUP
		|| nodeType == ANODE_SHADOWSETUP)
		return nImage;

	if (type == APARAM_FOV)
		nImage = 2;
	else if (type == APARAM_POS)
		nImage = 3;
	else if (type == APARAM_ROT)
		nImage = 4;
	else if (type == APARAM_SCL)
		nImage = 5;
	else if (type == APARAM_EVENT || type == APARAM_TRACKEVENT)
		nImage = 6;
	else if (type == APARAM_VISIBLE)
		nImage = 7;
	else if (type == APARAM_CAMERA)
		nImage = 8;
	else if (type == APARAM_SOUND)
		nImage = 9;
	else if (type == APARAM_ANIMATION)
		nImage = 10;
	else if (type == APARAM_SEQUENCE)
		nImage = 11;
	else if (type == APARAM_EXPRESSION)
		nImage = 12;
	else if (type == APARAM_FLOAT_1)
		nImage = 13;
	else if (type == APARAM_CAPTURE)
		nImage = 25;
	else if (type == APARAM_CONSOLE)
		nImage = 15;
	else if (type == APARAM_MUSIC)
		nImage = 19;
	else if (type == APARAM_FACE_SEQUENCE)
		nImage = 12;
	else if (type == APARAM_LOOKAT)
		nImage = 17;
	else if (type == APARAM_TIMEWARP)
		nImage = 22;
	else if (type == APARAM_COMMENT_TEXT)
		nImage = 23;
	else if (type == APARAM_SHAKEMULT || type == APARAM_NOISE)
		nImage = 28;

	return nImage;
}

//////////////////////////////////////////////////////////////////////////
int CTrackViewNodesCtrl::GetIconIndexForNode(EAnimNodeType type) const
{
	int nImage = 0;
	if (type == ANODE_ENTITY)
		nImage = 21;
	else if (type == ANODE_SCENE)
		nImage = 27;
	else if (type == ANODE_CAMERA)
		nImage = 8;
	else if (type == ANODE_CVAR)
		nImage = 15;
	else if (type == ANODE_SCRIPTVAR)
		nImage = 14;
	else if (type == ANODE_MATERIAL)
		nImage = 16;
	else if (type == ANODE_EVENT)
		nImage = 6;
	else if (type == ANODE_GROUP)
		nImage = 1;
	else if (type == ANODE_LAYER)
		nImage = 20;
	else if (type == ANODE_COMMENT)
		nImage = 23;
	else if (type == ANODE_LIGHT)
		nImage = 18;
	else if (type == ANODE_HDRSETUP)
		nImage = 26;
	else if (type == ANODE_SHADOWSETUP)
		nImage = 24;

	return nImage;
}

//////////////////////////////////////////////////////////////////////////
void CTrackViewNodesCtrl::FillNodes(CRecord *pRecord, IAnimNode *pParent)
{
	IObjectManager *pObjMgr = GetIEditor()->GetObjectManager();
	IAnimSequence *seq = m_sequence;

	const COLORREF TEXT_COLOR_FOR_MISSING_ENTITY = RGB(255,0,0);
	const COLORREF TEXT_COLOR_FOR_INVALID_MATERIAL = RGB(255,0,0);
	const COLORREF BACK_COLOR_FOR_ACTIVE_DIRECTOR = RGB(192,192,255);
	const COLORREF BACK_COLOR_FOR_INACTIVE_DIRECTOR = RGB(224,224,224);

	for (int i = 0; i < seq->GetNodeCount(); i++)
	{
		IAnimNode *node = seq->GetNode(i);
		if (node->GetParent() != pParent)
			continue;

		if (node->GetType() == ANODE_GROUP)
		{
			CRecord *pGroupRecord = new CRecord(true,node->GetName());
			CXTPReportRecordItem *pItem = new CXTPReportRecordItemText(node->GetName());
			pItem->SetBold(TRUE);
			pItem->SetBackgroundColor(RGB(220,255,220));
			pGroupRecord->AddItem(pItem);
			if (node->GetFlags()&ANODE_FLAG_EXPANDED)
			{
				pGroupRecord->SetExpanded(TRUE);
			}
			pGroupRecord->SetItemHeight(20);
			pGroupRecord->node = node;

			pRecord->GetChilds()->Add( pGroupRecord );

			FillNodes(pGroupRecord, node);
		}
		else
		{
			EAnimNodeType nodeType = node->GetType();
			int nNodeImage = GetIconIndexForNode(nodeType);

			CString nodeName = node->GetName();
			if (node->IsBoneLinkTransformEnabled())
			{
				// If the bone link transform is on, mark it in the node name properly.
				assert(nodeType == ANODE_ENTITY || nodeType == ANODE_CAMERA);
				CBaseObject *pObject = GetIEditor()->GetObjectManager()->FindAnimNodeOwner(node);
				assert(pObject);
				if (pObject)
				{
					nodeName += "-";
					int boneLinkIndex = static_cast<CEntityObject*>(pObject)->GetBoneAttachLinkIndex();
					if (boneLinkIndex >= 0)
					{
						nodeName += static_cast<CEntityObject*>(pObject)->GetEntityLink(boneLinkIndex).name;
					}
					else
					{
						nodeName += "Bone Link Not Found!";
					}
				}
			}
			CRecord *pNodeRecord = new CRecord( false,nodeName );
			CXTPReportRecordItem *pNodeItem = new CXTPReportRecordItemText(nodeName);
			pNodeRecord->AddItem(pNodeItem);
			pNodeItem->SetIconIndex(nNodeImage);
			pNodeItem->SetBold(TRUE);

			if (nodeType == ANODE_ENTITY || nodeType == ANODE_CAMERA)
			{
				if (node->GetEntity() == NULL)
				// In case of a missing entity,
				{
					// Make it red.
					pNodeItem->SetTextColor(TEXT_COLOR_FOR_MISSING_ENTITY);
				}
			}
				
			if (nodeType == ANODE_MATERIAL)
			{
				// Check if a valid material can be found by the node name.
				IMaterial *pMtl = NULL;
				CString matName;
				int subMtlIndex = GetMatNameAndSubMtlIndexFromName(matName, node->GetName());
				pMtl = gEnv->p3DEngine->GetMaterialManager()->FindMaterial(matName);
				if (pMtl)
				{
					bool bMultiMat = pMtl->GetSubMtlCount() > 0;
					bool bMultiMatWithoutValidIndex = bMultiMat && (subMtlIndex<0 || subMtlIndex>=pMtl->GetSubMtlCount());
					bool bLeafMatWithIndex = !bMultiMat && subMtlIndex != -1;
					if (bMultiMatWithoutValidIndex || bLeafMatWithIndex)
						pMtl = NULL;
				}

				if (pMtl == NULL)
					pNodeItem->SetTextColor(TEXT_COLOR_FOR_INVALID_MATERIAL);
			}

			// Mark the active director and other directors properly.
			if (node == m_sequence->GetActiveDirector())
				pNodeItem->SetBackgroundColor(BACK_COLOR_FOR_ACTIVE_DIRECTOR);
			else if (nodeType == ANODE_SCENE)
				pNodeItem->SetBackgroundColor(BACK_COLOR_FOR_INACTIVE_DIRECTOR);

			pNodeRecord->node = node;
			pNodeRecord->baseObj = pObjMgr->FindAnimNodeOwner(node);
			pNodeRecord->track = 0;
			pNodeRecord->paramId = 0;

			m_itemInfos.push_back(pNodeRecord);

			if (node->GetFlags()&ANODE_FLAG_EXPANDED)
			{
				pNodeRecord->SetExpanded(TRUE);
			}

			pRecord->GetChilds()->Add( pNodeRecord );

			FillTracks(pNodeRecord, node);
	
			// A director node also can has child nodes.
			if (nodeType == ANODE_SCENE)
				FillNodes(pNodeRecord, node);
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CTrackViewNodesCtrl::OnFillItems()
{
	m_itemInfos.clear();

	IAnimSequence *seq = m_sequence;
	if (!seq)
		return;

	string seqName = seq->GetFullName();
	CRecord *pRootGroupRec = new CRecord(true,seqName.c_str());
	CXTPReportRecordItem *pRootItem = new CXTPReportRecordItemText(seqName.c_str());
	pRootItem->SetBold(TRUE);
	pRootGroupRec->SetItemHeight(24);
	pRootGroupRec->AddItem(pRootItem);
	pRootGroupRec->SetExpanded(TRUE);

	AddRecord( pRootGroupRec );

	FillNodes(pRootGroupRec);

	// Additional empty record like space for scrollbar in key control
	CRecord *pGroupRec = new CRecord();
	pGroupRec->SetItemHeight(18);
	AddRecord( pGroupRec );
}

//////////////////////////////////////////////////////////////////////////
void CTrackViewNodesCtrl::OnItemExpanded( CXTPReportRow *pRow,bool bExpanded )
{
	CRecord *pRecord = (CRecord*)pRow->GetRecord();
	if (pRecord && pRecord->track)
	{
		IAnimTrack *track = pRecord->track;
		if (bExpanded)
		{
			// Mark track expanded
			track->SetFlags( track->GetFlags()|IAnimTrack::ATRACK_EXPANDED );
		}
		else
		{
			// Mark track collapsed.
			track->SetFlags( track->GetFlags()&(~IAnimTrack::ATRACK_EXPANDED) );
		}
	}
	else if (pRecord && pRecord->node)
	{
		IAnimNode *node = pRecord->node;
		if (bExpanded)
		{
			// Mark node expanded
			node->SetFlags( node->GetFlags()|ANODE_FLAG_EXPANDED );
		}
		else
		{
			// Mark node collapsed.
			node->SetFlags( node->GetFlags()&(~ANODE_FLAG_EXPANDED) );
		}
	}

	SyncKeyCtrl();
}

//////////////////////////////////////////////////////////////////////////
void CTrackViewNodesCtrl::OnSelectionChanged()
{
	m_keysCtrl->ClearSelection();
	
	if (!m_sequence)
		return;

	// Clear track selections.
	for (int i = 0; i < m_sequence->GetNodeCount(); i++)
	{
		IAnimNode *node = m_sequence->GetNode(i);
		for (int t = 0; t < node->GetTrackCount(); t++)
		{
			node->GetTrackByIndex(t)->SetSelected(false);
		}
	}


	int nCount = GetSelectedRows()->GetCount();
	for (int i = 0; i < nCount; i++)
	{
		CRecord *pRecord = (CRecord*)GetSelectedRows()->GetAt(i)->GetRecord();
		if (!pRecord->track)
			continue;

		for (int i = 0; i < m_keysCtrl->GetCount(); i++)
		{
			if (pRecord->track)
				pRecord->track->SetSelected(true);
			if (m_keysCtrl->GetTrack(i) == pRecord->track)
			{
				m_keysCtrl->SelectItem(i);
				break;
			}
		}
	}

	GetIEditor()->Notify( eNotify_OnUpdateTVKeySelection );
}



//////////////////////////////////////////////////////////////////////////
void CTrackViewNodesCtrl::SyncKeyCtrl()
{
	if (!m_keysCtrl)
		return;

	m_keysCtrl->ResetContent();

	if (!m_sequence)
		return;

	// Forces ctrl to be drawn first.
	UpdateWindow();

	int nStartRow = 0;

	CXTPReportRows *pRows = GetRows();
	int nNumRows = pRows->GetCount();
	for (int i = GetTopRowIndex(); i < nNumRows; i++)
	{
		CXTPReportRow *pRow = pRows->GetAt(i);
		if (!pRow->IsItemsVisible())
			break;

		CRecord *pRecord = (CRecord*)pRow->GetRecord();

		if (!pRecord)
			continue;

		CRect rc = pRecord->GetRect();
		rc = pRow->GetRect();

		int nItemHeight = rc.bottom-rc.top;

		CTrackViewDopeSheet::Item item;

		if (pRecord->track != NULL && pRecord->node != NULL)
		{
			item = CTrackViewDopeSheet::Item(nItemHeight, pRecord->node,pRecord->paramId,pRecord->track);
		}
		else
		{
			item = CTrackViewDopeSheet::Item(nItemHeight,pRecord->node);
		}
		item.nHeight = nItemHeight;
		item.bSelected = false;
		if (pRecord->track)
		{
			item.bSelected = (pRecord->track->GetFlags() & IAnimTrack::ATRACK_SELECTED) != 0;
		}
		m_keysCtrl->AddItem( item );
	}
}

//////////////////////////////////////////////////////////////////////////
void CTrackViewNodesCtrl::ExpandNode( IAnimNode *node )
{
	CXTPReportRows *pRows = GetRows();
	for (int i = 0,nNumRows = pRows->GetCount(); i < nNumRows; i++)
	{
		CXTPReportRow *pRow = pRows->GetAt(i);
		CRecord *pRecord = (CRecord*)pRow->GetRecord();
		if (pRecord && pRecord->node == node)
		{
			pRow->SetExpanded(TRUE);
			break;
		}
	}
	SyncKeyCtrl();
}

//////////////////////////////////////////////////////////////////////////
void CTrackViewNodesCtrl::DeleteSelectedNodes()
{
	char *description = NULL;

	bool bLightAnimationSetActive = m_sequence && (m_sequence->GetFlags() & IAnimSequence::LIGHT_ANIMATION_SET);

	Records records;
	GetSelectedRecords( records );

	// Check if any reference to the light animation to be deleted exists, and abort the removal, if any.
	if (bLightAnimationSetActive)
	{
		std::vector<CBaseObject*> entityObjects;
		GetIEditor()->GetObjectManager()->FindObjectsOfType(RUNTIME_CLASS(CEntityObject), entityObjects);

		for (int currentNode = 0; currentNode < records.size(); ++currentNode)
		{
			CRecord *pItemInfo = static_cast<CRecord*>(records[currentNode]);
			bool bNodeToBeDeleted = pItemInfo != NULL && pItemInfo->node != NULL && pItemInfo->track == NULL;
			if (bNodeToBeDeleted)
			{
				assert(pItemInfo->node->GetType() == ANODE_LIGHT);
				if (pItemInfo->node->GetType() == ANODE_LIGHT)
				{
					for (size_t i=0; i < entityObjects.size(); ++i)
					{
						CString lightAnimationName = static_cast<CEntityObject*>(entityObjects[i])->GetLightAnimation();
						if (lightAnimationName == pItemInfo->node->GetName())
						{
							CString errorMsg;
							errorMsg.Format("The node of '%s' cannot be removed since there is a light entity still using it.", 
								pItemInfo->node->GetName());
							AfxMessageBox(errorMsg, MB_ICONEXCLAMATION | MB_OK);
							return;
						}
					}
				}
			}
		}
	}

	if (GetSelectedCount() > 1)
	{
		description = "Remove Anim Nodes/Tracks";
	}
	else
	{
		description = "Remove Anim Node";
	}

	CUndo undo(description);
	CUndo::Record( new CUndoAnimSequenceObject(m_sequence) );

	bool nodeDeleted = false;
	bool trackDeleted = false;

	// First remove all selected tracks
	for (int currentNode = 0; currentNode < records.size(); currentNode++)
	{
		CRecord *pItemInfo = (CRecord*)records[currentNode];
		if (pItemInfo != NULL && pItemInfo->node != NULL && pItemInfo->track != NULL)
		{
			pItemInfo->node->RemoveTrack(pItemInfo->track);
			trackDeleted = true;
		}
	}

	// Then remove all selected nodes
	for (int currentNode = 0; currentNode < records.size(); currentNode++)
	{
		CRecord *pItemInfo = (CRecord*)records[currentNode];
		if (pItemInfo != NULL && pItemInfo->node != NULL && pItemInfo->track == NULL)
		{
			// Removing a node can involve re-parentings of its children if the node is a group.
			// So the validity of its re-parenting should be checked beforehand in that case.
			if (pItemInfo->node->GetType() == ANODE_GROUP || pItemInfo->node->GetType() == ANODE_SCENE)
			{
				bool bValidRemoval = true;
				CString offendingNodeName;
				for (int i=0; i<m_sequence->GetNodeCount(); ++i)
				{
					IAnimNode *pNode = m_sequence->GetNode(i);
					if (pNode->GetParent() == pItemInfo->node)	// Found a child
					{
						if (IsValidReparenting(pNode, 0) == false)
						{
							offendingNodeName = pNode->GetName();
							bValidRemoval = false;
							break;
						}
					}
				}

				if (bValidRemoval == false)
				{
					CString errorMsg;
					errorMsg.Format("The node of '%s' cannot be removed since it makes more than one nodes for '%s' active at the same time.", 
													pItemInfo->node->GetName(), offendingNodeName);
					AfxMessageBox(errorMsg, MB_ICONEXCLAMATION | MB_OK);
					// Skip this removal.
					continue;
				}
			}

			// Unbind the entity from the AnimNode before removing it.
			CEntityObject *pEntity = (CEntityObject*)GetIEditor()->GetObjectManager()->FindAnimNodeOwner(pItemInfo->node);
			if (pEntity)
			{
				pItemInfo->node->SetNodeOwner(0);
				pEntity->SetAnimNode(0);
			}

			m_sequence->RemoveNode(pItemInfo->node);
			nodeDeleted = true;
		}
	}

	if (nodeDeleted == true || trackDeleted == true)
	{
		InvalidateNodes();
	}
}

//////////////////////////////////////////////////////////////////////////
void CTrackViewNodesCtrl::SelectNode( const char *sName, bool bEnsureVisible )
{
	bool found = false;
	CXTPReportRows *pRows = GetRows();
	for (int i = 0,nNumRows = pRows->GetCount(); i < nNumRows; i++)
	{
		CXTPReportRow *pRow = pRows->GetAt(i);
		CRecord *pRecord = (CRecord*)pRow->GetRecord();
		pRow->SetSelected(FALSE);
		if (found == false && pRecord && pRecord->node != NULL 
			&& stricmp(pRecord->node->GetName(),sName) == 0)
		{
			pRow->SetSelected(TRUE);
			SetFocusedRow(pRow);
			if (bEnsureVisible)
				pRow->EnsureVisible();
			found = true;
		}
	}
	SyncKeyCtrl();
}

//////////////////////////////////////////////////////////////////////////
void CTrackViewNodesCtrl::SelectInViewPort()
{
	AnimNodes records;
	GetSelectedNodes( records );
	int selectedCount = records.size();

	IAnimNode *aNode = NULL;
	CBaseObject *selectedObject = NULL;

	bool bLightAnimationSetActive = m_sequence && (m_sequence->GetFlags() & IAnimSequence::LIGHT_ANIMATION_SET);
	if (bLightAnimationSetActive)
	{
		std::vector<CBaseObject*> objectsToBeSelected;
		std::vector<CBaseObject*> entityObjects;
		GetIEditor()->GetObjectManager()->FindObjectsOfType(RUNTIME_CLASS(CEntityObject), entityObjects);
		for (size_t i=0; i < entityObjects.size(); ++i)
		{
			CString lightAnimationName = static_cast<CEntityObject*>(entityObjects[i])->GetLightAnimation();
			if (lightAnimationName.IsEmpty())
				continue;
			for (int k=0; k < selectedCount; ++k)
			{
				if (lightAnimationName == records[k]->GetName())
				{
					objectsToBeSelected.push_back(entityObjects[i]);
					break;
				}
			}
		}

		if (objectsToBeSelected.empty())
			return;

		GetIEditor()->BeginUndo();
		GetIEditor()->ClearSelection();

		for (size_t i=0; i < objectsToBeSelected.size(); ++i)
		{
			GetIEditor()->SelectObject(objectsToBeSelected[i]);
		}

		GetIEditor()->AcceptUndo("Select Object(s)");
	}
	else
	{
		if (selectedCount>0)
		{
			GetIEditor()->BeginUndo();
			GetIEditor()->ClearSelection();
		}

		for (int i=0 ; i < selectedCount ; ++i)
		{
			aNode = records[i];
			CBaseObject *obj = GetIEditor()->GetObjectManager()->FindAnimNodeOwner(aNode);
			if (obj)
			{
				GetIEditor()->SelectObject(obj);
			}
		}

		if (selectedCount>0)
		{
			GetIEditor()->AcceptUndo("Select Object(s)");
		}
	}
}

//////////////////////////////////////////////////////////////////////////
bool CTrackViewNodesCtrl::CopySelectedNodes()
{
	XmlNodeRef animNodesRoot = XmlHelpers::CreateXmlNode("CopyAnimNodesRoot");

	AnimNodes records;
	GetSelectedNodes( records );

	if(records.empty() == false)
		m_sequence->CopyNodes(animNodesRoot, &(records[0]), (uint32)records.size());

	CClipboard clipboard;
	clipboard.Put(animNodesRoot,"Track view entity node");

	return true;
}

//////////////////////////////////////////////////////////////////////////
bool CTrackViewNodesCtrl::PasteNodes( IAnimNode *pGroupNode )
{
	CClipboard clipboard;
	if (clipboard.IsEmpty())
	{
		return false;
	}

	XmlNodeRef animNodesRoot = clipboard.Get();
	if (animNodesRoot == NULL || strcmp(animNodesRoot->getTag(),"CopyAnimNodesRoot"))
	{
		return false;
	}

	// If the active sequence is the light animation set,
	// check if all nodes are of the light node type.
	bool bLightAnimationSetActive = m_sequence && (m_sequence->GetFlags() & IAnimSequence::LIGHT_ANIMATION_SET);
	if (bLightAnimationSetActive)
	{
		bool bNonLightNodeExists = false;
		for (int i = 0; i < animNodesRoot->getChildCount(); ++i)
		{
			XmlNodeRef xn = animNodesRoot->getChild(i);

			int type;
			if (!xn->getAttr("Type", type))
				continue;

			if ((EAnimNodeType)type != ANODE_LIGHT)
			{
				bNonLightNodeExists = true;
				break;
			}
		}

		if (bNonLightNodeExists)
		{
			AfxMessageBox("Only nodes of the light node type can be added to the light animation set. Paste aborted.", MB_ICONEXCLAMATION | MB_OK);
			return false;
		}
	}

	// Check whether there is any name collision.
	bool bNodeOfSameNameExists = false;
	for (int i = 0; i < animNodesRoot->getChildCount(); ++i)
	{
		XmlNodeRef xn = animNodesRoot->getChild(i);

		int type;
		if (!xn->getAttr( "Type",type ))
			continue;
		
		const char *name = xn->getAttr("Name");
		if ((EAnimNodeType)type != ANODE_SCENE
		&& HasNode(name))
		{
			bNodeOfSameNameExists = true;
			break;
		}
	}
	// A name collision occurred. Abort the operation.
	if (bNodeOfSameNameExists)
	{
		AfxMessageBox("A node of the same name already exists. Paste aborted.", MB_ICONEXCLAMATION | MB_OK);
		return false;
	}

	bool bSaveUndo = !CUndo::IsRecording();

	if (bSaveUndo)
	{
		GetIEditor()->BeginUndo();
	}

	CUndo::Record(new CUndoAnimSequenceObject(m_sequence));

	m_sequence->PasteNodes(animNodesRoot, pGroupNode);

	InvalidateNodes();

	if (bSaveUndo)
	{
		GetIEditor()->AcceptUndo("Paste Anim Node(s)");
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////
void CTrackViewNodesCtrl::OnVerticalScroll( UINT nSBCode, UINT nPos, CScrollBar* pScrollBar )
{
	SyncKeyCtrl();
}

//////////////////////////////////////////////////////////////////////////
void CTrackViewNodesCtrl::OnNMRclick(NMHDR *pNMHDR, LRESULT *pResult)
{
	CPoint point;

	SItemInfo *pItemInfo = 0;

	if (!m_sequence)
		return;

	// Find node under mouse.
	GetCursorPos( &point );
	ScreenToClient( &point );
	// Select the item that is at the point myPoint.

	CXTPReportRow* pRow = HitTest(point);
	if (pRow)
	{
		pItemInfo = (SItemInfo*)pRow->GetRecord();
	}

	IAnimNode *pGroupNode = 0;
	if (pItemInfo && pItemInfo->node 
		&& (pItemInfo->node->GetType() == ANODE_GROUP || pItemInfo->node->GetType() == ANODE_SCENE))
	{
		pGroupNode = pItemInfo->node;
	}

	int cmd = ShowPopupMenu(point, pItemInfo);

	float scrollPos = SaveVerticalScrollPos();

	if (cmd == MI_SAVE_TO_COLLADA)
	{
		SaveNodeToCollada();
	}
	else if (cmd == MI_LOAD_FROM_COLLADA)
	{
		LoadNodeFromCollada();
	}
	else if (cmd == MI_SET_AS_VIEW_CAMERA)
	{
		SetAsViewCamera();
	}
	else if (cmd == MI_REMOVE_SELECTED)
	{
		DeleteSelectedNodes();
	}
	else if (cmd == MI_ADD_SELECTED_ENTITY)
	{
		m_pTrackViewDialog->AddSelectedNodes(pGroupNode);
	}
	else if (cmd == MI_ADD_CURRENT_LAYER)
	{
		m_pTrackViewDialog->AddCurrentLayer(pGroupNode);
	}
	else if (cmd == MI_ADD_SCREENFADER_NODE)
	{
		m_pTrackViewDialog->CreateAnimNode( ANODE_SCREENFADER, "ScreenFader", pGroupNode);
	}
	else if (cmd == MI_ADD_COMMENT_NODE)
	{
		m_pTrackViewDialog->CreateAnimNode( ANODE_COMMENT, "Comment",pGroupNode );
	}
	else if (cmd == MI_ADD_RADIALBLUR)
	{
		m_pTrackViewDialog->CreateAnimNode( ANODE_RADIALBLUR, "RadialBlur",pGroupNode);
	}
	else if (cmd == MI_ADD_COLLORCORRECTION)
	{
		m_pTrackViewDialog->CreateAnimNode( ANODE_COLORCORRECTION, "ColorCorrection",pGroupNode);
	}
	else if (cmd == MI_ADD_DOF)
	{
		m_pTrackViewDialog->CreateAnimNode( ANODE_DOF, "DepthOfField",pGroupNode);
	}
	else if (cmd == MI_ADD_HDR_SETUP)
	{
		m_pTrackViewDialog->CreateAnimNode( ANODE_HDRSETUP, "HdrSetup",pGroupNode);
	}
	else if (cmd == MI_ADD_SHADOW_SETUP)
	{
		m_pTrackViewDialog->CreateAnimNode( ANODE_SHADOWSETUP, "ShadowsSetup",pGroupNode);
	}
	else if (cmd == MI_ADD_DIRECTOR_NODE)
	{
		m_pTrackViewDialog->CreateAnimNode( ANODE_SCENE,
			m_pTrackViewDialog->GetAvailableNameForDirectorNode(),pGroupNode );
	}
	else if (cmd == MI_ADD_CONSOLE_VARIABLE)
	{
		CStringDlg dlg( _T("Console Variable Name") );
		if (dlg.DoModal() == IDOK && !dlg.GetString().IsEmpty())
		{
			m_pTrackViewDialog->CreateAnimNode( ANODE_CVAR,dlg.GetString(),pGroupNode );
		}
	}
	else if (cmd == MI_ADD_SCRIPT_VARIABLE)
	{
		CStringDlg dlg( _T("Script Variable Name") );
		if (dlg.DoModal() == IDOK && !dlg.GetString().IsEmpty())
		{
			m_pTrackViewDialog->CreateAnimNode( ANODE_SCRIPTVAR,dlg.GetString(),pGroupNode );
		}
	}
	else if (cmd == MI_ADD_MATERIAL)
	{
		CStringDlg dlg( _T("Material Name") );
		if (dlg.DoModal() == IDOK && !dlg.GetString().IsEmpty())
		{
			m_pTrackViewDialog->CreateAnimNode( ANODE_MATERIAL,dlg.GetString(),pGroupNode );
		}
	}
	else if (cmd == MI_ADD_EVENT)
	{
		CStringDlg dlg( _T("Track Event Name") );
		if (dlg.DoModal() == IDOK && !dlg.GetString().IsEmpty())
		{
			m_pTrackViewDialog->CreateAnimNode( ANODE_EVENT,dlg.GetString(),pGroupNode );
		}
	}
	else if (cmd == MI_EDIT_EVENTS)
	{
		EditEvents();
	}
	else if (cmd == MI_RENAME && pItemInfo != NULL && pItemInfo->node != NULL)
	{
		RenameNode(pItemInfo);

	}
	else if (cmd == MI_CREATE_FOLDER)
	{
		CreateFolder(pGroupNode);
	}
	else if (cmd == MI_DETACH_FROM_GROUP)
	{
		// Detach from group
		if (pItemInfo && pItemInfo->node)
		{
			IAnimNode *pNode = pItemInfo->node;
			SetNodeParent(pNode, 0);
			InvalidateNodes();
			SelectNode(pNode->GetName());
		}
	}
	else if (cmd == MI_SET_AS_ACTIVE_DIRECTOR)
	{
		if (pItemInfo && pItemInfo->node)
		{
			assert(pItemInfo->node->GetType() == ANODE_SCENE);
			m_sequence->SetActiveDirector(pItemInfo->node);
			InvalidateNodes();
		}
	}
	else if (cmd == MI_TRANSFORM_BONE_LINK)
	{
		if (pItemInfo && pItemInfo->node)
		{
			assert(pItemInfo->node->GetType() == ANODE_ENTITY
					|| pItemInfo->node->GetType() == ANODE_CAMERA);
			if (pItemInfo->node->IsBoneLinkTransformEnabled())
				pItemInfo->node->EnableBoneLinkTransform(false);
			else
				pItemInfo->node->EnableBoneLinkTransform(true);
			InvalidateNodes();
		}
	}
	else if (cmd >= MI_ADD_TRACK_BASE && cmd < MI_ADD_TRACK_BASE+1000)
	{
		if (pItemInfo)
		{
			IAnimNode *node = pItemInfo->node;
			if (node)
			{
				AddTrack(cmd-MI_ADD_TRACK_BASE, node);
			}
		}
	}
	else if (cmd == MI_REMOVE_TRACK)
	{
		if (pItemInfo)
		{
			IAnimNode *node = pItemInfo->node;
			if (node)
			{
				RemoveTrack( pItemInfo);
			}
		}
	}
	else if (cmd >= MI_SHOW_HIDE_BASE && cmd < MI_SHOW_HIDE_BASE+100)
	{
		if (pItemInfo)
		{
			IAnimNode *node = pItemInfo->node;
			if (node)
			{
				ShowHideTrack(node, cmd-MI_SHOW_HIDE_BASE);
			}
		}
	}
	else if(cmd==MI_COPY_SELECTED_KEYS)
	{
		m_keysCtrl->CopyKeys();
	}
	else if(cmd==MI_PASTE_KEYS)
	{
		SItemInfo * pSii = GetSelectedNode();
		if(pSii)
			m_keysCtrl->PasteKeys(pSii->node,pSii->track,0);
		else
			m_keysCtrl->PasteKeys(0,0,0);
	}
	else if(cmd==MI_COPY_SELECTED_NODES)
	{
		CopySelectedNodes();
	}
	else if(cmd==MI_PASTE_NODES)
	{
		PasteNodes(pGroupNode);
	}
	else if(cmd==MI_EXPAND_ALL)
		ExpandNodes();
	else if(cmd==MI_COLLAPSE_ALL)
		CollapseNodes();
	else if(cmd==MI_EXPAND_FOLDERS)
		ExpandNodes(ANODE_GROUP);
	else if(cmd==MI_COLLAPSE_FOLDERS)
		CollapseNodes(ANODE_GROUP);
	else if(cmd==MI_EXPAND_ENTITIES)
		ExpandNodes(ANODE_ENTITY);
	else if(cmd==MI_COLLAPSE_ENTITIES)
		CollapseNodes(ANODE_ENTITY);
	else if(cmd==MI_EXPAND_CAMERAS)
		ExpandNodes(ANODE_CAMERA);
	else if(cmd==MI_COLLAPSE_CAMERAS)
		CollapseNodes(ANODE_CAMERA);
	else if(cmd==MI_EXPAND_MATERIALS)
		ExpandNodes(ANODE_MATERIAL);
	else if(cmd==MI_COLLAPSE_MATERIALS)
		CollapseNodes(ANODE_MATERIAL);
	else if(cmd==MI_EXPAND_EVENTS)
		ExpandNodes(ANODE_EVENT);
	else if(cmd==MI_COLLAPSE_EVENTS)
		CollapseNodes(ANODE_EVENT);
	else if (cmd==MI_SELECT_IN_VIEWPORT )
	{
		SelectInViewPort();
	}
	else if (cmd >= MI_SELECT_SUBMTL_BASE && cmd < MI_SELECT_SUBMTL_BASE+100)
	{
		if (pItemInfo)
		{
			IAnimNode *node = pItemInfo->node;
			if (node)
			{
				assert(node->GetType() == ANODE_MATERIAL);
				CString matName;
				int subMtlIndex = GetMatNameAndSubMtlIndexFromName(matName, node->GetName());
				CString newMatName;
				newMatName.Format("%s.[%d]", matName.GetString(), cmd-MI_SELECT_SUBMTL_BASE+1);
				node->SetName(newMatName);
				InvalidateNodes();
				SelectNode(newMatName);
			}
		}
	}

	if(cmd)
	{
		RestoreVerticalScrollPos(scrollPos);
		SyncKeyCtrl();
	}

	// processed
	*pResult = 1;
}

//////////////////////////////////////////////////////////////////////////
CTrackViewNodesCtrl::CRecord* CTrackViewNodesCtrl::GetSelectedNode()
{
	if (GetSelectedCount() == 1)
	{
		Records records;
		GetSelectedRecords( records );
		return (CRecord*)records[0];
	}
	return 0;
}

//////////////////////////////////////////////////////////////////////////
bool CTrackViewNodesCtrl::OnBeginDragAndDrop( CXTPReportSelectedRows* pRows, CPoint pt )
{
	if( pRows->GetCount() == 0 )
		return false;	

	for( int i = 0; i < pRows->GetCount(); ++i )
	{
		CRecord* pRec = static_cast<CRecord*>((pRows->GetAt(i))->GetRecord());
		if (!pRec || !pRec->node )
			return false;	
		if (pRec->node && pRec->track && !pRec->IsGroup())
			return false;
	}
	return __super::OnBeginDragAndDrop(pRows,pt);
}

//////////////////////////////////////////////////////////////////////////
void CTrackViewNodesCtrl::OnDragAndDrop( CXTPReportSelectedRows* pRows, CPoint absoluteCursorPos )
{
	if( pRows->GetCount() == 0 )
		return;

	CPoint p = absoluteCursorPos;
	ScreenToClient(&p);

	CRect reportArea = GetReportRectangle();
	if (reportArea.PtInRect(p))
	{
		CXTPReportRow* pTargetRow = HitTest( p );
		bool bContainTarget = pRows->Contains(pTargetRow);

		if( pTargetRow )
		{
			CRecord *pFirstRecordSrc = (CRecord*)(pRows->GetAt(0)->GetRecord());
			CRecord *pRecordTrg = (CRecord*)pTargetRow->GetRecord();
			if(pFirstRecordSrc && pRecordTrg && pFirstRecordSrc->node && pRecordTrg->node && !bContainTarget )
			{
				const char* srcFirstNodeName = pFirstRecordSrc->node->GetName();
				IAnimNode *pTargetNode = pRecordTrg->node;
				if(pTargetNode->GetType() == ANODE_GROUP || pTargetNode->GetType() == ANODE_SCENE)
				{
					CAnimSequenceUndo undo(m_sequence,"Attach Anim Node to Group");
					AnimNodes nodes;
					if (GetSelectedNodes( nodes ))
					{
						for (int i = 0; i < (int)nodes.size(); i++)
						{
							if (nodes[i] != pTargetNode)
								SetNodeParent(nodes[i], pTargetNode);
						}
					}
				}
				else
				{
					CAnimSequenceUndo undo(m_sequence,"Reorder Node");
					IAnimNode *pTargetNode = pRecordTrg->node;

					for( size_t i = 0; i < pRows->GetCount(); ++i )
					{
						CRecord *pRecord = static_cast<CRecord*>(pRows->GetAt(i)->GetRecord());
						SetNodeParent(pRecord->node, pTargetNode->GetParent());
					}

					for( size_t i = 0; i < pRows->GetCount(); ++i )
					{
						CRecord *pRecord = static_cast<CRecord*>(pRows->GetAt(pRows->GetCount()-i-1)->GetRecord());
						CRect targetRect = pTargetRow->GetRect();
						CRect upperTargetRect;
						CPoint br = targetRect.BottomRight();
						br.y -= targetRect.Height() / 2;
						upperTargetRect.SetRect(targetRect.TopLeft(), br);
						bool next = upperTargetRect.PtInRect(p) == FALSE;
						m_sequence->ReorderNode( pRecord->node, pTargetNode, next );
					}
				}

				InvalidateNodes();
				SelectNode( srcFirstNodeName );
			}
			else
			{
				if( pRecordTrg && pRecordTrg == GetRows()->GetAt(0)->GetRecord() )
				{
					CAnimSequenceUndo undo(m_sequence,"Detach Anim Node from Group");
					for( size_t i = 0; i < pRows->GetCount(); ++i )
					{
						CRecord *pRecord = static_cast<CRecord*>(pRows->GetAt(pRows->GetCount()-i-1)->GetRecord());
						if(pRecord->node->GetParent())
							SetNodeParent(pRecord->node, 0);
						else
							m_sequence->ReorderNode( pRecord->node, NULL, false );
					}
					InvalidateNodes();
				}
			}
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CTrackViewNodesCtrl::OnItemDblClick( CXTPReportRow *pRow )
{
	CRecord *pRecord = (CRecord*)pRow->GetRecord();
	if (pRecord && pRecord->node != NULL)
	{
		// Double Clicked on node or track.
		// Make this object selected in Editor.
		CBaseObject *obj = GetIEditor()->GetObjectManager()->FindAnimNodeOwner(pRecord->node);
		if (obj)
		{
			CUndo undo( "Select Object(s)" );
			GetIEditor()->ClearSelection();
			GetIEditor()->SelectObject(obj);
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CTrackViewNodesCtrl::InvalidateNodes()
{
	GetIEditor()->Notify( eNotify_OnUpdateTrackView );
}

//////////////////////////////////////////////////////////////////////////
bool CTrackViewNodesCtrl::GetSelectedNodes( AnimNodes &nodes )
{
	int nCount = GetSelectedRows()->GetCount();
	for (int i = 0; i < nCount; i++)
	{
		CRecord *pRecord = (CRecord*)GetSelectedRows()->GetAt(i)->GetRecord();
		if (pRecord && pRecord->node)
			stl::push_back_unique(nodes, pRecord->node);
	}

	// If its ancestor node is already in the list, remove it from the list.
	for (size_t i=0; i<nodes.size(); )
	{
		IAnimNode *pParentNode = nodes[i]->GetParent();
		bool bAncestorFound = false;
		while(pParentNode)
		{
			if(stl::find(nodes, pParentNode))
			{
				std::swap(nodes[i], nodes[nodes.size()-1]);
				nodes.pop_back();
				bAncestorFound = true;
				break;
			}
			pParentNode = pParentNode->GetParent();
		}

		if(bAncestorFound == false)
			++i;
	}

	return !nodes.empty();
}

//////////////////////////////////////////////////////////////////////////
bool CTrackViewNodesCtrl::HasNode(const char *name) const
{
	if(name == NULL)
		return false;

	IAnimNode *pNode = NULL;
	pNode = m_sequence->FindNodeByName(name);
	if (pNode)
		return true;
	else
		return false;
}

//////////////////////////////////////////////////////////////////////////
void CTrackViewNodesCtrl::ExpandNodes(EAnimNodeType nodeType)
{
	for (int i = 0,num = m_sequence->GetNodeCount(); i < num; i++)
	{
		IAnimNode *node = m_sequence->GetNode(i);
		if (nodeType == ANODE_ANYTRACK || node->GetType() == nodeType)
			node->SetFlags( node->GetFlags() | ANODE_FLAG_EXPANDED );
	}
	InvalidateNodes();
}

//////////////////////////////////////////////////////////////////////////
void CTrackViewNodesCtrl::CollapseNodes(EAnimNodeType nodeType)
{
	for (int i = 0,num = m_sequence->GetNodeCount(); i < num; i++)
	{
		IAnimNode *node = m_sequence->GetNode(i);
		if (nodeType == ANODE_ANYTRACK || node->GetType() == nodeType)
		 node->SetFlags( node->GetFlags() & ~ANODE_FLAG_EXPANDED );
	}
	InvalidateNodes();
}

void CTrackViewNodesCtrl::SaveNodeToCollada()
{
	SItemInfo * pSii = GetSelectedNode();
	if (pSii && pSii->node)
	{		
		char szFilters[] = "Collada Files (*.dae)|*.dae|";

		CString path = Path::GetGameFolder() + CString("\\");
		CString file = CString(pSii->node->GetName()) + CString(".dae");

		CExportColladaDlg dlg(path, file);
		ENodeExportType iExportType = NODE_EXPORT_GLOBAL;
		CString finalPath = "";
		float fps = 30.0f;
		int dlgRes = -1;
		
		while( (dlgRes = dlg.DoModal()) == IDOK && !dlg.IsPathSet())
		{
			AfxMessageBox("Oops! You didn't set the output path. Exporting aborted.", MB_ICONWARNING|MB_OK);
		}

		if(dlgRes == IDCANCEL)
			return;		

		switch(dlg.GetExportType())
		{
		case 0:
			iExportType = NODE_EXPORT_GLOBAL;
			break;
		case 1:
			iExportType = NODE_EXPORT_LOCAL;
			break;
		default:
			break;
		}

		finalPath = dlg.GetExportPath();			
		fps = dlg.GetFPS();

		//-------------------------------------------------
		// Get current sequence
		IAnimSequence* currSeq = m_pTrackViewDialog->GetCurrentSequence();
		SItemInfo * pSii = GetSelectedNode();

		// Get entity
		IEntity* pEntity = NULL;
		if (pSii && pSii->node)// && pSii->node->GetType() == ANODE_CAMERA)
		{
			CBaseObject *pObject = GetIEditor()->GetObjectManager()->FindAnimNodeOwner(pSii->node);
			if( pObject && pObject->IsKindOf(RUNTIME_CLASS(CEntityObject)) )
			{
				CEntityObject * ent = static_cast<CEntityObject*> (pObject);
				if(ent)
					pEntity = ent->GetIEntity();
			}
		}

		if(currSeq && pEntity)
		{
			XmlNodeRef colladaNode = pSii->node->SaveToColladaInFixedFPS(fps, iExportType, currSeq, pEntity);
			colladaNode->saveToFile(finalPath.GetString());	
		}		
	}
}

void CTrackViewNodesCtrl::LoadNodeFromCollada()
{
	SItemInfo * pSii = GetSelectedNode();
	if (pSii && pSii->node)
	{		
		char szFilters[] = "Collada Files (*.dae)|*.dae|";

		CString path = Path::GetGameFolder() + CString("\\");

		CString filePath;
		if (CFileUtil::SelectFile(szFilters, path, filePath))
		{
			XmlNodeRef colladaNode = GetISystem()->LoadXmlFromFile(filePath.GetString());
			if(!colladaNode)
				AfxMessageBox("Cannot load the specified COLLADA file.", MB_ICONEXCLAMATION | MB_OK);
			else
			{
				bool ok = pSii->node->LoadFromCollada(colladaNode);
				if (ok == false)
					AfxMessageBox("Cannot load the specified COLLADA file.", MB_ICONEXCLAMATION | MB_OK);
				GetIEditor()->UpdateTrackView();
			}
		}
	}
}

void CTrackViewNodesCtrl::SetAsViewCamera()
{
	SItemInfo * pSii = GetSelectedNode();
	if (pSii && pSii->node && pSii->node->GetType() == ANODE_CAMERA)
	{
		CBaseObject *pCameraObject = GetIEditor()->GetObjectManager()->FindAnimNodeOwner(pSii->node);
		if (pCameraObject)
			static_cast<CRenderViewport*>(GetIEditor()->GetViewManager()->GetGameViewport())->SetCameraObject(pCameraObject);
	}
}

void CTrackViewNodesCtrl::EditEvents()
{
	CTVEventsDialog dlg(m_sequence);
	dlg.DoModal();
}

void CTrackViewNodesCtrl::RenameNode( SItemInfo * pItemInfo )
{
	// Rename node under cursor.
	CStringDlg dlg( _T("Rename Node") );
	CString oldName = pItemInfo->node->GetName();
	dlg.SetString(oldName);
	if (dlg.DoModal() == IDOK)
	{
		CString name = dlg.GetString();
		pItemInfo->node->SetName( name );
		InvalidateNodes();
		SelectNode(name.GetString());

		bool bLightAnimationSetActive = m_sequence && (m_sequence->GetFlags() & IAnimSequence::LIGHT_ANIMATION_SET);
		if (bLightAnimationSetActive)
			UpdateLightAnimationRefs(oldName , name);
	}
}

void CTrackViewNodesCtrl::CreateFolder( IAnimNode * pGroupNode )
{
	// Change Group of the node.
	CStringDlg dlg( _T("Enter Folder Name") );
	//dlg.SetString(pItemInfo->node->GetGroupName());
	if (dlg.DoModal() == IDOK)
	{
		CString name = dlg.GetString();
		if(m_sequence->FindNodeByName(name))
			AfxMessageBox("The name already exists. Use another.", MB_ICONEXCLAMATION | MB_OK);
		else
		{
			CAnimSequenceUndo undo(m_sequence,"Create Group");
			m_pTrackViewDialog->CreateAnimNode( ANODE_GROUP,name,pGroupNode );
			InvalidateNodes();
			SelectNode(name.GetString());
		}
	}
}

void CTrackViewNodesCtrl::AddTrack( int paramId, IAnimNode * node )
{
	CUndo undo("Create Anim Track");
	CUndo::Record( new CUndoAnimSequenceObject(m_sequence) );

	node->CreateTrack( paramId );
	InvalidateNodes();
	ExpandNode(node);
}

void CTrackViewNodesCtrl::RemoveTrack( SItemInfo * pItemInfo )
{
	CUndo undo("Remove Anim Track");
	CUndo::Record( new CUndoAnimSequenceObject(m_sequence) );

	pItemInfo->node->RemoveTrack(pItemInfo->track);
	InvalidateNodes();
}

void CTrackViewNodesCtrl::ShowHideTrack( IAnimNode * node, int trackIndex )
{
	CUndo undo("Modify Sequence");
	CUndo::Record( new CUndoAnimSequenceObject(m_sequence) );

	IAnimTrack *track = node->GetTrackByIndex(trackIndex);

	// change hidden flag for this track.
	if (track->GetFlags() & IAnimTrack::ATRACK_HIDDEN)
		track->SetFlags( track->GetFlags() & ~IAnimTrack::ATRACK_HIDDEN );
	else
		track->SetFlags( track->GetFlags() | IAnimTrack::ATRACK_HIDDEN );

	InvalidateNodes();
}

int CTrackViewNodesCtrl::ShowPopupMenu( CPoint point, const SItemInfo *pItemInfo )
{
	// Create pop up menu.
	CMenu menu;
	CMenu menuAddTrack;

	CMenu menuExpand;
	CMenu menuCollapse;

	menu.CreatePopupMenu();

	bool bLightAnimationSetActive = m_sequence && (m_sequence->GetFlags() & IAnimSequence::LIGHT_ANIMATION_SET);

	IAnimNode *pGroupNode = 0;
	if (pItemInfo && pItemInfo->node 
		&& (pItemInfo->node->GetType() == ANODE_GROUP || pItemInfo->node->GetType() == ANODE_SCENE))
	{
		pGroupNode = pItemInfo->node;
	}

	if (GetSelectedCount() > 1)
	{
		Records records;
		GetSelectedRecords( records );
		bool nodeSelected = false;
		for (int currentNode = 0; currentNode < GetSelectedCount(); currentNode++)
		{
			SItemInfo *pItemInfo = (SItemInfo*)records[currentNode];
			if (pItemInfo != NULL && pItemInfo->node != NULL && pItemInfo->track == NULL)
			{
				nodeSelected = true;
				break;
			}
		}

		if (nodeSelected)
		{
			menu.AppendMenu( MF_STRING,MI_COPY_SELECTED_NODES,"Copy Selected Nodes" );
		}

		menu.AppendMenu( MF_STRING,MI_REMOVE_SELECTED,"Remove Selected Nodes/Tracks" );

		if (nodeSelected)
		{
			menu.AppendMenu( MF_SEPARATOR,0,"" );
			menu.AppendMenu( MF_STRING, MI_SELECT_IN_VIEWPORT ,"Select In Viewport" );
		}

	}
	else
	{
		bool notOnValidItem = !pItemInfo || !pItemInfo->node;
		bool onValidItem = !notOnValidItem;
		bool onGroupNode = pGroupNode != NULL;
		bool onNode = onValidItem && pItemInfo->track == NULL;
		bool onTrackNotSub = 
			onValidItem && pItemInfo->track != NULL && pItemInfo->m_bIsSubTrack == false;
		if (notOnValidItem || onGroupNode)
		{
			menuExpand.CreatePopupMenu();
			menuCollapse.CreatePopupMenu();

			menuExpand.AppendMenu( MF_STRING,MI_EXPAND_ALL,_T("Expand all") );
			menuCollapse.AppendMenu( MF_STRING,MI_COLLAPSE_ALL,_T("Collapse all") );
			menuExpand.AppendMenu( MF_STRING,MI_EXPAND_FOLDERS,_T("Expand Folders") );
			menuCollapse.AppendMenu( MF_STRING,MI_COLLAPSE_FOLDERS,_T("Collapse Folders") );
			menuExpand.AppendMenu( MF_STRING,MI_EXPAND_ENTITIES,_T("Expand Entities") );
			menuCollapse.AppendMenu( MF_STRING,MI_COLLAPSE_ENTITIES,_T("Collapse Entities") );
			menuExpand.AppendMenu( MF_STRING,MI_EXPAND_CAMERAS,_T("Expand Cameras") );
			menuCollapse.AppendMenu( MF_STRING,MI_COLLAPSE_CAMERAS,_T("Collapse Cameras") );
			menuExpand.AppendMenu( MF_STRING,MI_EXPAND_MATERIALS,_T("Expand Materials") );
			menuCollapse.AppendMenu( MF_STRING,MI_COLLAPSE_MATERIALS,_T("Collapse Materials") );
			menuExpand.AppendMenu( MF_STRING,MI_EXPAND_EVENTS,_T("Expand Events") );
			menuCollapse.AppendMenu( MF_STRING,MI_COLLAPSE_EVENTS,_T("Collapse Events") );

			menu.AppendMenu(MF_POPUP,(UINT_PTR)menuExpand.m_hMenu,"Expand");
			menu.AppendMenu(MF_POPUP,(UINT_PTR)menuCollapse.m_hMenu,"Collapse");

			if (!bLightAnimationSetActive)
			{
				menu.AppendMenu( MF_SEPARATOR,0,"" );
				if (onGroupNode)
				{
					// In case that it's a director node instead of a normal group node,
					if (pItemInfo->node->GetType() == ANODE_SCENE)
					{
						menu.AppendMenu( MF_STRING,MI_SET_AS_ACTIVE_DIRECTOR,"Set as Active Director" );
						menu.AppendMenu( MF_SEPARATOR,0,"" );
						if (pItemInfo->node->GetParent())
							menu.AppendMenu( MF_STRING,MI_DETACH_FROM_GROUP,"Detach from Group" );
						menu.AppendMenu( MF_STRING,MI_COPY_SELECTED_KEYS,"Copy Selected Keys");
						menu.AppendMenu( MF_STRING,MI_PASTE_KEYS,"Paste Keys");
					}
					menu.AppendMenu( MF_STRING,MI_COPY_SELECTED_NODES,"Copy" );
					menu.AppendMenu( MF_STRING,MI_RENAME,"Rename" );
					menu.AppendMenu( MF_STRING,MI_REMOVE_SELECTED,"Delete" );
				}
				menu.AppendMenu( MF_STRING,MI_CREATE_FOLDER,"Create Folder" );
				menu.AppendMenu( MF_SEPARATOR,0,"" );
				menu.AppendMenu( MF_STRING,MI_ADD_SELECTED_ENTITY,_T("Add Selected Entity(s)") );

				if( m_pTrackViewDialog && !m_pTrackViewDialog->IsCurrentLayerInSequence())
					menu.AppendMenu( MF_STRING,MI_ADD_CURRENT_LAYER,_T("Add Current Layer") );

				//! 
				menu.AppendMenu( MF_SEPARATOR,0,"" );

				if(m_pTrackViewDialog && !m_pTrackViewDialog->IsAnimNodeInCurrentSequence(ANODE_RADIALBLUR))
					menu.AppendMenu( MF_STRING, MI_ADD_RADIALBLUR,_T("Add Radial Blur Node"));

				if(m_pTrackViewDialog && !m_pTrackViewDialog->IsAnimNodeInCurrentSequence(ANODE_COLORCORRECTION))
					menu.AppendMenu( MF_STRING, MI_ADD_COLLORCORRECTION,_T("Add Color Correction Node"));

				if(m_pTrackViewDialog && !m_pTrackViewDialog->IsAnimNodeInCurrentSequence(ANODE_DOF))
					menu.AppendMenu( MF_STRING, MI_ADD_DOF,_T("Add Depth of Field Node"));

				if(m_pTrackViewDialog && !m_pTrackViewDialog->IsAnimNodeInCurrentSequence(ANODE_SCREENFADER))
					menu.AppendMenu( MF_STRING,MI_ADD_SCREENFADER_NODE,_T("Add Screen Fader") );

				if(m_pTrackViewDialog && !m_pTrackViewDialog->IsAnimNodeInCurrentSequence(ANODE_HDRSETUP))
					menu.AppendMenu( MF_STRING, MI_ADD_HDR_SETUP,_T("Add HDR Setup Node"));

				if(m_pTrackViewDialog && !m_pTrackViewDialog->IsAnimNodeInCurrentSequence(ANODE_SHADOWSETUP))
					menu.AppendMenu( MF_STRING, MI_ADD_SHADOW_SETUP,_T("Add Shadows Setup Node"));

				menu.AppendMenu( MF_SEPARATOR,0,"" );
				// A director node cannot have another director node as a child.
				if (notOnValidItem || pItemInfo->node->GetType() != ANODE_SCENE)
					menu.AppendMenu( MF_STRING,MI_ADD_DIRECTOR_NODE,_T("Add Director(Scene) Node") );
				menu.AppendMenu( MF_STRING,MI_ADD_COMMENT_NODE,_T("Add Comment Node") );
				menu.AppendMenu( MF_STRING,MI_ADD_CONSOLE_VARIABLE,_T("Add Console Variable") );
				menu.AppendMenu( MF_STRING,MI_ADD_SCRIPT_VARIABLE,_T("Add Script Variable") );
				menu.AppendMenu( MF_STRING,MI_ADD_MATERIAL,_T("Add Material") );
				menu.AppendMenu( MF_STRING,MI_ADD_EVENT,_T("Add Event") );
				menu.AppendMenu( MF_SEPARATOR,0,"" );
				menu.AppendMenu( MF_STRING,MI_EDIT_EVENTS,_T("Edit Events") );
			}
			menu.AppendMenu( MF_SEPARATOR,0,"" );
			menu.AppendMenu( MF_STRING,MI_PASTE_NODES,_T("Paste Node(s)") );
		}
		else	// On a node other than group node or on a track
		{
			if (onNode)
			{
				if (!bLightAnimationSetActive)
				{
					menu.AppendMenu( MF_STRING,MI_CREATE_FOLDER,"Create Folder" );
					if (pItemInfo->node->GetParent())
						menu.AppendMenu( MF_STRING,MI_DETACH_FROM_GROUP,"Detach from Group" );
					menu.AppendMenu( MF_SEPARATOR,0,"" );
				}
				menu.AppendMenu( MF_STRING,MI_COPY_SELECTED_NODES,"Copy" );
				menu.AppendMenu( MF_STRING,MI_COPY_SELECTED_KEYS,"Copy Selected Keys");
				menu.AppendMenu( MF_SEPARATOR,0,"" );
				menu.AppendMenu( MF_STRING,MI_REMOVE_SELECTED,"Delete" );
			}
			else	// On a track
			{
				menu.AppendMenu( MF_STRING,MI_COPY_SELECTED_KEYS,"Copy Keys");
			}
			if (onValidItem)
			{
				if (pItemInfo->node->GetFlags() & ANODE_FLAG_CAN_CHANGE_NAME
					|| bLightAnimationSetActive)
				{
					menu.AppendMenu( MF_STRING,MI_RENAME,"Rename" );
				}
			}

			menu.AppendMenu( MF_STRING,MI_PASTE_KEYS,"Paste Keys");

			if (onNode)
			{
				menu.AppendMenu( MF_SEPARATOR,0,"" );
				menu.AppendMenu( MF_STRING,MI_SELECT_IN_VIEWPORT,"Select In Viewport" );
			}

			if (onNode && !bLightAnimationSetActive)
			{
				menu.AppendMenu( MF_SEPARATOR,0,"" );
				bool bEntityOrCameraNode = pItemInfo->node->GetType() == ANODE_ENTITY 
																|| pItemInfo->node->GetType() == ANODE_CAMERA;
				UINT flag = bEntityOrCameraNode?MF_STRING:(MF_STRING|MF_DISABLED);
				menu.AppendMenu( flag,MI_SAVE_TO_COLLADA,_T("Save to a COLLADA file...") );
				menu.AppendMenu( flag,MI_LOAD_FROM_COLLADA,_T("Load from a COLLADA file...") );

				if (bEntityOrCameraNode)
				{
					bool bHasBoneLink = false;
					CBaseObject *pObject = GetIEditor()->GetObjectManager()->FindAnimNodeOwner(pItemInfo->node);
					if (pObject)
					{
						if (static_cast<CEntityObject*>(pObject)->GetBoneAttachLinkIndex() >= 0)
							bHasBoneLink = true;
					}
					flag = bHasBoneLink?MF_STRING:(MF_STRING|MF_DISABLED);
					bool bBoneLinkXformEnabled = pItemInfo->node->IsBoneLinkTransformEnabled();
					menu.AppendMenu( flag,MI_TRANSFORM_BONE_LINK,bBoneLinkXformEnabled?	_T("Transform Entity") :
																																							_T("Transform Bone Link") );
				}

				bool bMatNode = pItemInfo->node->GetType() == ANODE_MATERIAL;
				if (bMatNode)
				{
					CString matName;
					int subMtlIndex = GetMatNameAndSubMtlIndexFromName(matName, 
																					pItemInfo->node->GetName());
					IMaterial *pMtl = gEnv->p3DEngine->GetMaterialManager()->FindMaterial(matName);
					bool bMultMatNode = pMtl ? pMtl->GetSubMtlCount() > 0 : false;
					if (bMultMatNode)
					{
						menu.AppendMenu( MF_SEPARATOR,0,"" );
						for (int k=0; k<pMtl->GetSubMtlCount(); ++k)
						{
							flag = MF_STRING;
							if (k == subMtlIndex)
								flag |= MF_CHECKED;
							CString subMtlName;
							subMtlName.Format("[%d] %s", k+1, pMtl->GetSubMtl(k)->GetName());
							menu.AppendMenu(flag, MI_SELECT_SUBMTL_BASE+k, subMtlName);
						}
					}
				}

				if (pItemInfo->node->GetType() == ANODE_CAMERA)
				{
					menu.AppendMenu( MF_SEPARATOR,0,"" );
					menu.AppendMenu( MF_STRING,MI_SET_AS_VIEW_CAMERA,_T("Set As View Camera") );
				}
			}
		}

		// add tracks menu
		menuAddTrack.CreatePopupMenu();
		bool bTracksToAdd=false;
		if (onValidItem)
		{
			menu.AppendMenu( MF_SEPARATOR,0,"" );
			IAnimNode::SParamInfo paramInfo;
			// List`s which tracks can be added to animation node.
			for (int i = 0; i < pItemInfo->node->GetParamCount(); i++)
			{
				if (!pItemInfo->node->GetParamInfo( i,paramInfo ))
					continue;

				int flags = 0;
				IAnimTrack *track = pItemInfo->node->GetTrackForParameter( paramInfo.paramId );
				if (track && !(paramInfo.flags&IAnimNode::PARAM_MULTIPLE_TRACKS))
				{
					continue;
					//flags |= MF_CHECKED;
				}

				menuAddTrack.AppendMenu( MF_STRING|flags,MI_ADD_TRACK_BASE+paramInfo.paramId,paramInfo.name );
				bTracksToAdd=true;
			}
		}
		if (bTracksToAdd)
			menu.AppendMenu(MF_POPUP,(UINT_PTR)menuAddTrack.m_hMenu,"Add Track");

		// delete track menu
		if (onTrackNotSub)
		{
			menu.AppendMenu(MF_STRING, MI_REMOVE_TRACK, "Remove Track");
		}

		if (bTracksToAdd || onTrackNotSub)
			menu.AppendMenu( MF_SEPARATOR,0,"" );

		if (onValidItem)
		{
			CString str;
			str.Format( "%s Tracks",pItemInfo->node->GetName() );
			menu.AppendMenu( MF_STRING|MF_DISABLED,0,str );

			// Show tracks in anim node.
			{
				IAnimNode::SParamInfo paramInfo;
				for (int i = 0; i < pItemInfo->node->GetTrackCount(); i++)
				{
					IAnimTrack *track = pItemInfo->node->GetTrackByIndex(i);

					if (!pItemInfo->node->GetParamInfoFromId( track->GetParameterType(),paramInfo ))
						continue;

					// change hidden flag for this track.
					int checked = MF_CHECKED;
					if (track->GetFlags() & IAnimTrack::ATRACK_HIDDEN)
					{
						checked = MF_UNCHECKED;
					}
					menu.AppendMenu( MF_STRING|checked,MI_SHOW_HIDE_BASE+i,CString( "  " ) + paramInfo.name );
				}
			}
		}
	}

	GetCursorPos( &point );
	// track menu

	if(m_bEditLock)
		SetPopupMenuLock(&menu);

	return CXTPCommandBars::TrackPopupMenu(&menu, TPM_NONOTIFY|TPM_RETURNCMD|TPM_LEFTALIGN|TPM_RIGHTBUTTON, 
																					point.x,point.y, this, NULL);
}

//-----------------------------------------------------------------------------
void CTrackViewNodesCtrl::SetPopupMenuLock(CMenu* menu)
{
	if(!m_bEditLock || !menu)
		return;

	UINT count = menu->GetMenuItemCount();
	for(UINT i=0; i<count;++i)
	{
		CString menuString;
		menu->GetMenuString(i,menuString, MF_BYPOSITION);

		if(menuString!="Expand" && menuString!="Collapse")
			menu->EnableMenuItem(i,MF_DISABLED | MF_GRAYED | MF_BYPOSITION );
	}
}


void CTrackViewNodesCtrl::OnSize(UINT nType, int cx, int cy) 
{
	__super::OnSize(nType, cx, cy);

	const int kHeight = 20;
	const int kStaticWidth = 50;
	// Reposition the filter edit control.
	if(m_filterEdit.GetSafeHwnd())
	{
		CRect rect;
		GetClientRect(&rect);
		m_filterEdit.MoveWindow(0, rect.Height()-kHeight, rect.Width()-kStaticWidth, kHeight);
		m_matchCountStatic.MoveWindow(rect.Width()-kStaticWidth, rect.Height()-kHeight, kStaticWidth, kHeight);
	}

	SyncKeyCtrl();
}

//-----------------------------------------------------------------------------
BOOL CTrackViewNodesCtrl::OnEraseBkgnd(CDC* pDC)
{
	CXTPReportPaintManager* pPaintManager = GetPaintManager();
	if(pPaintManager)
	{
		COLORREF backGround(COLORREF_NULL);
		if(m_bEditLock)
			backGround = EDIT_DISABLE_GRAY_COLOR;

		pPaintManager->m_clrControlBack.SetCustomValue(backGround);
		
	}
	return TRUE;
}

//-----------------------------------------------------------------------------
void CTrackViewNodesCtrl::SetLayerNodeAnimators()
{
	if(!m_sequence)
		return;

	CObjectLayerManager* pLayerManager =
		GetIEditor()->GetObjectManager()->GetLayersManager();
	if(pLayerManager)
		pLayerManager->SetLayerNodeAnimators(m_sequence);
}

float CTrackViewNodesCtrl::SaveVerticalScrollPos() const
{
	int sbMin=0, sbMax=0;
	GetScrollRange(SB_VERT, &sbMin, &sbMax);
	return float(GetTopRowIndex()) / std::max(float(sbMax - sbMin), 1.0f);
}

void CTrackViewNodesCtrl::RestoreVerticalScrollPos(float fScrollPos)
{
	int sbMin=0, sbMax=0;
	GetScrollRange(SB_VERT, &sbMin, &sbMax);
	int newScrollPos = FloatToIntRet(fScrollPos * (sbMax - sbMin) + sbMin);
	SetTopRow(newScrollPos);
}

//////////////////////////////////////////////////////////////////////////
void CTrackViewNodesCtrl::OnMouseMove( UINT nFlags, CPoint point )
{
	__super::OnMouseMove(nFlags,point);

	if (m_pDragImage && m_pDragRows)
	{
		if( m_pDragRows->GetCount() == 0 )
			return;

		CPoint p = point;

		CRect reportArea = GetReportRectangle();
		if (reportArea.PtInRect(p))
		{
			CXTPReportRow* pTargetRow = HitTest( p );
			bool bContainTarget = m_pDragRows->Contains(pTargetRow);

			if( pTargetRow )
			{
				CRecord *pFirstRecordSrc = (CRecord*)(m_pDragRows->GetAt(0)->GetRecord());
				CRecord *pRecordTrg = (CRecord*)pTargetRow->GetRecord();
				if(pFirstRecordSrc && pRecordTrg && pFirstRecordSrc->node && pRecordTrg->node && !bContainTarget )
				{
					IAnimNode *pTargetNode = pRecordTrg->node;
					// In case of pushing into a group
					if(pTargetNode->GetType() == ANODE_GROUP || pTargetNode->GetType() == ANODE_SCENE)
					{
						SetCursor(m_downRightCursor);
					}
					// In case of reordering nodes
					else
					{
						CRect targetRect = pTargetRow->GetRect();
						CRect upperTargetRect;
						CPoint br = targetRect.BottomRight();
						br.y -= targetRect.Height() / 2;
						upperTargetRect.SetRect(targetRect.TopLeft(), br);
						if(upperTargetRect.PtInRect(p))
							SetCursor(m_upCursor);
						else
							SetCursor(m_downCursor);
					}
				}
				// In case of pulling out from a group
				else
				{
					SetCursor(m_upRightCursor);
				}
			}
		}	
	}
}

void CTrackViewNodesCtrl::MoveJustCreatedNodeBelowSelected()
{
	IAnimNode *pSelectedNode = NULL;
	CTrackViewNodesCtrl::SItemInfo * pSii = GetSelectedNode();
	if (pSii && pSii->node)
		pSelectedNode = pSii->node;

	// Move the just created node to the below of a selected node, if any.
	// Just created node means the last node.
	IAnimNode *pJustCreatedNode = m_sequence->GetNode(m_sequence->GetNodeCount()-1);
	if (pSelectedNode && pSelectedNode->GetType() != ANODE_GROUP
										&& pSelectedNode->GetType() != ANODE_SCENE)
	{
		SetNodeParent(pJustCreatedNode, pSelectedNode->GetParent());
		m_sequence->ReorderNode(pJustCreatedNode, pSelectedNode, true);
	}
}

//////////////////////////////////////////////////////////////////////////
void CTrackViewNodesCtrl::FillAutoCompletionListForFilter(const IAnimSequence *pSeq)
{
	if(m_filterEdit.GetSafeHwnd() == NULL)
		return;
	m_filterEdit.RemoveSearchAll();
	if(pSeq == NULL)
		return;

	for(int i=0; i<pSeq->GetNodeCount(); ++i)
	{
		m_filterEdit.AddSearchString(pSeq->GetNode(i)->GetName());
	}
}

//////////////////////////////////////////////////////////////////////////
LRESULT CTrackViewNodesCtrl::OnFilterChange(WPARAM wParam, LPARAM lParam)
{
	bool first = true;
	if(wParam == EN_UPDATE && m_sequence)
	{
		m_currentMatchIndex = 0;	// Reset the match index
		m_matchCount = 0;					// and the count.
		CString filterText, nodeName;;
		m_filterEdit.GetWindowText(filterText);
		if(filterText.IsEmpty() == false)
		{
			filterText.MakeLower();
			for(int i=0; i<m_sequence->GetNodeCount(); ++i)
			{
				IAnimNode *pNode = m_sequence->GetNode(i);
				nodeName = pNode->GetName();
				nodeName.MakeLower();
				if(nodeName.Find(filterText) > -1)
				{
					if(first)
					{
						m_pTrackViewDialog->SnapToNode(pNode->GetName());
						first = false;
					}
					
					++m_matchCount;
				}
			}
		}

		CString matchCountText;
		matchCountText.Format("%d/%d", m_matchCount==0?0:1, m_matchCount);	// One-based indexing
		m_matchCountStatic.SetWindowText(matchCountText);
	}

	return FALSE;
}

//////////////////////////////////////////////////////////////////////////
bool CTrackViewNodesCtrl::IsValidReparenting(IAnimNode *pChild, IAnimNode *pParent) const
{
	bool bEntityNode = pChild->GetType() == ANODE_ENTITY ||
		pChild->GetType() == ANODE_CAMERA ||
		pChild->GetType() == ANODE_LIGHT;
	
	if(bEntityNode)
	{
		IAnimNode *pNewOwningDirector = NULL;
		if(pParent)
		{
			if(pParent->GetType() == ANODE_SCENE)
				pNewOwningDirector = pParent;
			else
				pNewOwningDirector = pParent->HasDirectorAsParent();
		}

		for(int i=0; i<m_sequence->GetNodeCount(); ++i)
		{
			IAnimNode *pNode = m_sequence->GetNode(i);
			if(pNode == pChild)
				continue;
			bool bNodeForSameEntityFound = strcmp(pNode->GetName(), pChild->GetName()) == 0
				&& pNode->GetType() == pChild->GetType();
			if(bNodeForSameEntityFound)
			{
				IAnimNode *pOwningDirector = pNode->HasDirectorAsParent();
				if(pOwningDirector == NULL || pNewOwningDirector == NULL ||
					pOwningDirector == pNewOwningDirector)
				{
					return false;
				}
			}
		}
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////
void CTrackViewNodesCtrl::SetNodeParent(IAnimNode *pChild, IAnimNode *pParent)
{
	if(IsValidReparenting(pChild, pParent) == false)
	{
		AfxMessageBox("Only one node can be active for a given entity at a time.", MB_ICONEXCLAMATION | MB_OK);
		return;
	}

	pChild->SetParent(pParent);
}

//////////////////////////////////////////////////////////////////////////
int CTrackViewNodesCtrl::GetMatNameAndSubMtlIndexFromName(CString& matName, const char *nodeName)
{
	const char * pCh;
	const int kMaxMatPath = 64;

	if (pCh = strstr(nodeName, ".["))
	{
		char matPath[kMaxMatPath];
		strncpy_s(matPath, kMaxMatPath, nodeName, pCh-nodeName);
		matPath[pCh-nodeName]=0;
		matName = matPath;
		pCh+=2;
		if ((*pCh) != 0)
		{
			int index = atoi(pCh)-1;
			return index;
		}
	}
	else
	{
		matName = nodeName;
	}

	return -1;
}

//////////////////////////////////////////////////////////////////////////
void CTrackViewNodesCtrl::OnFocusChanging(NMHDR* pNotifyStruct, LRESULT* result)
{
	if (!m_sequence)
		return;
	
	m_pTrackViewDialog->ClearTracksToolBar();

	XTP_NM_REPORTRECORDITEM* pItemNotify = (XTP_NM_REPORTRECORDITEM*) pNotifyStruct;
	if (!pItemNotify) 
		return;

	CXTPReportRow* pRowSel=pItemNotify->pRow;

	if (!pRowSel) 
		return;

	CRecord *pSii = (CRecord*)pRowSel->GetRecord();

	if (pSii && pSii->node)
	{
		m_pTrackViewDialog->SetNodeForTracksToolBar(pSii->node);
		IAnimNode::SParamInfo paramInfo;
		int paramCount = pSii->node->GetParamCount();
		for (int i = 0; i < paramCount; ++i)
		{
			if (!pSii->node->GetParamInfo( i,paramInfo ))
				continue;

			int flags = 0;
			IAnimTrack *track = pSii->node->GetTrackForParameter( paramInfo.paramId );
			if (track && !(paramInfo.flags&IAnimNode::PARAM_MULTIPLE_TRACKS))
			{
				continue;
			}

			m_pTrackViewDialog->AddButtonToTracksToolBar(paramInfo.paramId, 
				m_imageList.ExtractIcon(GetIconIndexForParam(paramInfo.paramId, pSii->node->GetType())));
		}
	}

}

//////////////////////////////////////////////////////////////////////////
BOOL CTrackViewNodesCtrl::PreTranslateMessage(MSG* pMsg) 
{
	if(pMsg->message == WM_KEYDOWN)
	{
		if(pMsg->hwnd == m_filterEdit.GetSafeHwnd())
		{
			if(pMsg->wParam == VK_TAB && m_matchCount > 1)
			{
				m_currentMatchIndex 
					= (m_currentMatchIndex + 1) % m_matchCount;	// Advance the current match index.

				UINT matchCount = 0;
				CString filterText, nodeName;
				m_filterEdit.GetWindowText(filterText);
				assert(filterText.IsEmpty() == false);
				filterText.MakeLower();
				for(int i=0; i<m_sequence->GetNodeCount(); ++i)
				{
					IAnimNode *pNode = m_sequence->GetNode(i);
					nodeName = pNode->GetName();
					nodeName.MakeLower();
					if(nodeName.Find(filterText) > -1)
					{
						if(matchCount == m_currentMatchIndex)
						{
							m_pTrackViewDialog->SnapToNode(pNode->GetName());
						}

						++matchCount;
					}
				}
				assert(m_matchCount == matchCount);
					
				CString matchCountText;
				matchCountText.Format("%d/%d", m_currentMatchIndex+1, matchCount);	// One-based indexing
				m_matchCountStatic.SetWindowText(matchCountText);

				return TRUE;
			}
		}
	}

	return __super::PreTranslateMessage(pMsg);
}

//////////////////////////////////////////////////////////////////////////
void CTrackViewNodesCtrl::UpdateLightAnimationRefs(const CString& oldName, const CString& newName)
{
	std::vector<CBaseObject*> entityObjects;
	GetIEditor()->GetObjectManager()->FindObjectsOfType(RUNTIME_CLASS(CEntityObject), entityObjects);
	std::for_each(entityObjects.begin(), entityObjects.end(), 
		[&oldName, &newName](CBaseObject *pBaseObject)
	{
		CEntityObject *pEntityObject = static_cast<CEntityObject*>(pBaseObject);
		bool bLight = pEntityObject && pEntityObject->GetEntityClass().Compare("Light") == 0;
		if(bLight)
		{
			CString lightAnimation = pEntityObject->GetEntityPropertyString("lightanimation_LightAnimation");
			if(lightAnimation == oldName)
			{
				//pEntityObject->SetEntityPropertyString("lightanimation_LightAnimation", newName);
				if (CVarBlock* pProperties = pEntityObject->GetProperties())
				{
					IVariable *pVariable = pProperties->FindVariable("lightanimation_LightAnimation");
					if (pVariable)
						pVariable->Set(newName);
				}
			}
		}
	});
}

//////////////////////////////////////////////////////////////////////////
void CTrackViewNodesCtrl::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	if(nChar == 'Z')
	{
		if(GetKeyState(VK_CONTROL))
		{
			GetIEditor()->Undo();
			return;
		}
	}

	__super::OnKeyDown(nChar, nRepCnt, nFlags);
}